package info.openrocket.core.file.motor;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import info.openrocket.core.motor.Manufacturer;
import info.openrocket.core.motor.Motor;
import info.openrocket.core.motor.MotorDigest;
import info.openrocket.core.motor.MotorDigest.DataType;
import info.openrocket.core.motor.ThrustCurveMotor;
import info.openrocket.core.util.CoordinateIF;
import info.openrocket.core.util.Coordinate;

public class RASPMotorLoader extends AbstractMotorLoader {

	public static final String CHARSET_NAME = "ISO-8859-1";

	public static final Charset CHARSET = Charset.forName(CHARSET_NAME);

	@Override
	protected Charset getDefaultCharset() {
		return CHARSET;
	}

	public List<ThrustCurveMotor.Builder> load(InputStream stream, String filename, boolean removeDelayFromDesignation)
			throws IOException {
		return load(new InputStreamReader(stream, getDefaultCharset()), filename, removeDelayFromDesignation);
	}

	/**
	 * Load a <code>Motor</code> from a RASP file specified by the
	 * <code>Reader</code>.
	 * The <code>Reader</code> is responsible for using the correct charset.
	 * <p>
	 * The CG is assumed to be located at the center of the motor casing and the
	 * mass
	 * is calculated from the thrust curve by assuming a constant exhaust velocity.
	 * 
	 * @param reader                     the source of the file.
	 * @param removeDelayFromDesignation if true, the motor delay will be left out
	 *                                   of the motor designation (e.g. "B6" instead
	 *                                   of "B6-0")
	 * @return a list of the {@link Motor} objects defined in the file.
	 * @throws IOException if an I/O error occurs or if the file format is illegal.
	 */
	public List<ThrustCurveMotor.Builder> load(Reader reader, String filename, boolean removeDelayFromDesignation)
			throws IOException {
		List<ThrustCurveMotor.Builder> motors = new ArrayList<>();

		String manufacturer = "";
		String designation = "";
		String comment = "";

		double length = 0;
		double diameter = 0;
		ArrayList<Double> delays = null;

		List<Double> time = new ArrayList<>();
		List<Double> thrust = new ArrayList<>();

		double propW = 0;
		double totalW = 0;

		try (BufferedReader in = new BufferedReader(reader)) {
			String line;
			String[] pieces, buf;

			line = in.readLine();
			main: while (line != null) { // Until EOF

				manufacturer = "";
				designation = "";
				comment = "";
				length = 0;
				diameter = 0;
				delays = new ArrayList<>();
				propW = 0;
				totalW = 0;
				time.clear();
				thrust.clear();

				// Read comment
				while (line.length() == 0 || line.charAt(0) == ';') {
					if (line.length() > 0) {
						comment += line.substring(1).trim() + "\n";
					}
					line = in.readLine();
					if (line == null)
						break main;
				}
				comment = comment.trim();

				// Parse header line, example:
				// F32 24 124 5-10-15-P .0377 .0695 RV
				// desig diam len delays prop.w tot.w manufacturer
				pieces = split(line);
				if (pieces.length != 7) {
					throw new IOException("Illegal file format. Motor header line must contain 7 fields:<br>" +
							"&nbsp designation diameter length delays propellantWeight totalWeight manufacturer");
				}

				designation = pieces[0];
				diameter = Double.parseDouble(pieces[1]) / 1000.0;
				length = Double.parseDouble(pieces[2]) / 1000.0;

				if (pieces[3].equalsIgnoreCase("None")) {

				} else {
					buf = split(pieces[3], "[-,]+");
					for (String s : buf) {
						if (s.equalsIgnoreCase("P") ||
								s.equalsIgnoreCase("plugged")) {
							delays.add(Motor.PLUGGED_DELAY);
						} else if (s.matches("[0-9]+")) {
							// Many RASP files have "100" as an only delay
							double d = Double.parseDouble(s);
							if (d < 99)
								delays.add(d);
						}
					}
					Collections.sort(delays);
				}

				propW = Double.parseDouble(pieces[4]);
				totalW = Double.parseDouble(pieces[5]);
				manufacturer = pieces[6];

				if (propW > totalW) {
					throw new IOException("Propellant weight exceeds total weight in " +
							"RASP file " + filename);
				}

				// Read the data
				for (line = in.readLine(); (line != null)
						&& (line.length() == 0 || line.charAt(0) != ';'); line = in.readLine()) {

					buf = split(line);
					if (buf.length == 0) {
						continue;
					} else if (buf.length == 2) {

						time.add(Double.parseDouble(buf[0]));
						thrust.add(Double.parseDouble(buf[1]));

					} else {
						throw new IOException("Illegal file format.<br>" +
								"Data should only have 2 entries: a time and thrust value.");
					}
				}

				// Comment of EOF encountered, marks the start of the next motor
				if (time.size() < 2) {
					throw new IOException("Illegal file format, too short thrust-curve.");
				}
				double[] delayArray = new double[delays.size()];
				for (int i = 0; i < delays.size(); i++) {
					delayArray[i] = delays.get(i);
				}
				motors.add(createRASPMotor(manufacturer, designation, comment,
						length, diameter, delayArray, propW, totalW, time, thrust, removeDelayFromDesignation));
			}

		} catch (NumberFormatException e) {

			throw new IOException("Illegal file format. Could not convert value to a number.<br" +
					">Verify that each number is correctly formatted.");

		}

		return motors;
	}

	@Override
	public List<ThrustCurveMotor.Builder> load(Reader reader, String filename) throws IOException {
		return load(reader, filename, true);
	}

	/**
	 * Create a motor from RASP file data.
	 * 
	 * @throws IOException if the data is illegal for a thrust curve
	 */
	private static ThrustCurveMotor.Builder createRASPMotor(String manufacturer, String designation,
			String comment, double length, double diameter, double[] delays,
			double propW, double totalW, List<Double> time, List<Double> thrust, boolean removeDelayFromDesignation)
			throws IOException {

		// Add zero time/thrust if necessary
		sortLists(time, thrust);
		finalizeThrustCurve(time, thrust);
		List<Double> mass = calculateMass(time, thrust, totalW, propW);

		double[] timeArray = new double[time.size()];
		double[] thrustArray = new double[time.size()];
		CoordinateIF[] cgArray = new CoordinateIF[time.size()];
		for (int i = 0; i < time.size(); i++) {
			timeArray[i] = time.get(i);
			thrustArray[i] = thrust.get(i);
			cgArray[i] = new Coordinate(length / 2, 0, 0, mass.get(i));
		}

		if (removeDelayFromDesignation) {
			designation = removeDelay(designation);
		}

		// Create the motor digest from data available in RASP files
		MotorDigest motorDigest = new MotorDigest();
		motorDigest.update(DataType.TIME_ARRAY, timeArray);
		motorDigest.update(DataType.MASS_SPECIFIC, totalW, totalW - propW);
		motorDigest.update(DataType.FORCE_PER_TIME, thrustArray);
		final String digest = motorDigest.getDigest();

		try {

			Manufacturer m = Manufacturer.getManufacturer(manufacturer);
			ThrustCurveMotor.Builder builder = new ThrustCurveMotor.Builder();
			builder.setManufacturer(m)
					.setDesignation(designation)
					.setDescription(comment)
					.setMotorType(m.getMotorType())
					.setStandardDelays(delays)
					.setDiameter(diameter)
					.setLength(length)
					.setTimePoints(timeArray)
					.setThrustPoints(thrustArray)
					.setCGPoints(cgArray)
					.setDigest(digest);
			return builder;

		} catch (IllegalArgumentException e) {

			// Bad data read from file.
			throw new IOException("Illegal file format.", e);

		}
	}
}
